package service

import (
	"encoding/base64"
	"encoding/binary"
	"errors"
	"fmt"
	gowin32wx "github.com/gorpher/gowin32"
	golnk "github.com/parsiya/golnk"
	uuid "github.com/satori/go.uuid"
	psnet "github.com/shirou/gopsutil/v3/net"
	"github.com/yusufpapurcu/wmi"
	"golang.org/x/sys/windows/registry"
	"hcy-api/lib/utils"
	"image/png"
	"io/fs"
	"net"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"runtime"
	"strings"
)

type HostPlugin struct {
}

// AppHost 主机基础信息
type AppHost struct {
	HostName       string `json:"hostName"`
	Mac            string `json:"mac "`
	Ip             string `json:"ip"`
	Domain         string `json:"domain"`         // eg:WORKGROUP, 域
	Os             string `json:"os"`             // 系统类型：windows / linux
	OsCaption      string `json:"osCaption"`      // eg: Microsoft Windows 10 专业版 , 系统标题
	PartOfDomain   bool   `json:"partOfDomain"`   // eg:false, 计算机是否入域
	SystemType     string `json:"systemType"`     // eg；x64-based PC ，系统类型
	BuildNumber    string `json:"buildNumber"`    // eg : 19043 .操作系统的构建号,它比产品发布版本号更精确的版本信息。
	OsArchitecture string `json:"osArchitecture"` // eg: 64位 ，操作系统位数
	Version        string `json:"version"`        // eg:10.0.19043 ,系统版本号
	SerialNumber   string `json:"serialNumber"`   // eg:00330-80191-48344-AA161, 操作系统产品序列号。
	RdpPort        uint8  `json:"rdpPort"`
	CpuInfo        string `json:"cpuInfo"`
}

func (s HostPlugin) getRegisterInfo() (info AppHost, err error) {
	var (
		s1  []SYSAbstractForWmi
		s2  []SYSOperatingForWmi
		s3  []CpuForWmi
		ip  string
		mac string
	)
	if err = wmi.Query("select  Name,PartOfDomain,SystemType,Domain  from Win32_ComputerSystem", &s1); err != nil {
		return
	}
	if err = wmi.Query("select BuildNumber,Caption,OSArchitecture,SerialNumber,SystemDrive,Version from Win32_OperatingSystem", &s2); err != nil {
		return
	}
	if err = wmi.Query("SELECT Name,NumberOfCores,NumberOfLogicalProcessors FROM Win32_Processor", &s3); err != nil {
		return
	}
	if len(s1) < 1 {
		err = errors.New("Win32_ComputerSystem 调用失败")
		return
	}
	if len(s2) < 1 {
		err = errors.New("Win32_OperatingSystem 调用失败")
		return
	}
	if len(s3) < 1 {
		err = errors.New("Win32_Processor 调用失败")
		return
	}
	if ip, mac, err = getIpMac(); err != nil {
		return
	}
	//if rdpPort, err = getRdpPort(); err != nil {
	//	return
	//}
	info = AppHost{
		HostName:       s1[0].Name,
		Ip:             ip,
		Mac:            mac,
		Domain:         s1[0].Domain,
		Os:             runtime.GOOS,
		OsCaption:      s2[0].Caption,
		PartOfDomain:   s1[0].PartOfDomain,
		SystemType:     s1[0].SystemType,
		BuildNumber:    s2[0].BuildNumber,
		OsArchitecture: s2[0].OSArchitecture,
		Version:        s2[0].Version,
		SerialNumber:   s2[0].SerialNumber,
		CpuInfo:        strings.TrimSpace(s3[0].Name),
	}
	return
}

// getIpMac 获取当前活跃ipv4地址和其对应的网卡地址
func getIpMac() (ip, mac string, err error) {
	var (
		p psnet.InterfaceStatList
	)
	if ip, err = getOutBoundIP(); err != nil {
		return
	}
	if p, err = psnet.Interfaces(); err != nil {
		return
	}
	for _, item := range p {
		if len(item.Addrs) > 0 {
			for _, inter := range item.Addrs {
				ipv4 := inter.Addr
				ipv4 = ipv4[:strings.LastIndex(ipv4, "/")]
				if ipv4 == ip {
					mac = item.HardwareAddr
					mac = strings.ReplaceAll(mac, ":", "-")
				}
			}
		}
	}
	return
}

func getOutBoundIP() (ip string, err error) {
	var (
		conn net.Conn
	)
	conn, err = net.Dial("udp", "8.8.8.8:9999")
	if err != nil {
		return "", err
	}
	localAdd := conn.LocalAddr()
	ip = strings.Split(localAdd.String(), ":")[0]
	return ip, nil
}

// getRdpPort 通过注册表获取rdp端口信息
func getRdpPort() (port uint, err error) {
	var (
		key   registry.Key
		pval  string
		nport int
	)
	if key, err = registry.OpenKey(registry.LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp", registry.ALL_ACCESS); err != nil {
		return
	}
	if pval, _, err = key.GetStringValue("PortNumber"); err != nil {
		return
	}
	nport, err = utils.ConvertHexPortToDecimal(pval)
	if err != nil {
		return
	}
	return uint(nport), err
}

type AppInfo struct {
	AppName   string `json:"appName"`
	LinkPath  string `json:"linkPath"` //应用路径，如果是快捷方式，快捷方式的路径
	ExePath   string `json:"exePath"`  // 应用实际执行文件的路径
	AppParams string `json:"appParams"`
	WorkSpace string `json:"workSpace"`
	Icon      string `json:"icon"`
	Remark    string `json:"remark"`
}

// getAppList 获取云应用信息
func (s HostPlugin) getAppList(lnkPath string) (apps []AppInfo, err error) {
	apps = make([]AppInfo, 0)
	err = filepath.Walk(lnkPath, func(fp string, info fs.FileInfo, err error) error {
		if strings.Trim(path.Ext(path.Base(fp)), ".") == "lnk" {
			var appInfo AppInfo
			appInfo, err = getAppInfoByLnk(fp)
			if err == nil {
				apps = append(apps, appInfo)
			}
		}
		return nil
	})
	return apps, nil
}

// getAppInfoByLnk 通过快捷方式获取应用信息
func getAppInfoByLnk(lnkPath string) (appInfo AppInfo, err error) {
	var flnk golnk.LnkFile
	flnk, err = golnk.File(lnkPath)
	if err != nil {
		err = errors.New(fmt.Sprintf("无效的lnk path:%s", lnkPath))
		return
	}
	var imgStr string
	if strings.HasSuffix(flnk.StringData.IconLocation, ".ico") {
		imgStr = icoToBase64(flnk.StringData.IconLocation)
	} else {
		imgStr = exeToBase64(flnk.LinkInfo.LocalBasePath)
	}
	linkName := filepath.Base(lnkPath)
	appName := strings.TrimSuffix(linkName, filepath.Ext(linkName))
	appInfo = AppInfo{
		AppName:   appName,
		LinkPath:  lnkPath,
		ExePath:   flnk.LinkInfo.LocalBasePath,
		AppParams: flnk.StringData.CommandLineArguments,
		WorkSpace: flnk.StringData.WorkingDir,
		Icon:      imgStr,
		Remark:    flnk.StringData.NameString,
	}
	return
}

// icoToBase64 将ico中的第一个图像转换为png格式
func icoToBase64(iconPath string) (str string) {
	var (
		f   *os.File
		err error
	)
	f, err = os.Open(iconPath)
	if err != nil {
		return
	}
	defer f.Close()
	// 读取ICO文件头
	var header struct {
		Reserved uint16
		Type     uint16
		Count    uint16
	}
	if err = binary.Read(f, binary.LittleEndian, &header); err != nil {
		return
	}

	// 读取图标目录
	dirEntries := make([]struct {
		Width    uint8
		Height   uint8
		Colors   uint8
		Reserved uint8
		Planes   uint16
		BitCount uint16
		Size     uint32
		Offset   uint32
	}, header.Count)
	if err = binary.Read(f, binary.LittleEndian, &dirEntries); err != nil {
		return
	}
	if len(dirEntries) < 1 {
		return
	}

	// 读取第一个图标数据
	_, _ = f.Seek(int64(dirEntries[0].Offset), 0)
	icoData := make([]byte, dirEntries[0].Size)
	if _, err = f.Read(icoData); err != nil {
		err = errors.New(fmt.Sprintf("无法读取ICO图标数据:%s", err))
		return
	}
	// 这个png图片有可能是可以打开但是转换为base64后浏览器无法识别的情况
	// 可能是看图软件做了兼容，具体情况后面在研究
	execPath, err := os.Executable()
	if err != nil {
		return
	}
	pngImg := filepath.Join(filepath.Dir(execPath), uuid.NewV4().String()+".png")
	pngFile, err := os.Create(pngImg)
	if err != nil {
		err = errors.New(fmt.Sprintf("无法创建PNG文件:%s", err))
		return
	}
	defer pngFile.Close()
	if _, err = pngFile.Write(icoData); err != nil {
		err = errors.New(fmt.Sprintf("无法写入PNG文件:%s", err))
		return
	}
	str = "data:image/png;base64," + base64.StdEncoding.EncodeToString(icoData)
	return
}

// exeToBase64 通过 windows 提取exe的图像信息
func exeToBase64(execPath string) (str string) {
	img, err := gowin32wx.ExtractPrivateExtractIcons(execPath, 128, 128)
	if err != nil {
		return ""
	}
	fp, err := os.Create("output.png")
	defer func() {
		fp.Close()
		os.Remove("output.png")
	}()
	if err != nil {
		return ""
	}
	err = png.Encode(fp, img)
	if err != nil {
		return ""
	}
	b, err := os.ReadFile("output.png")
	if err != nil {
		return ""
	}
	str = "data:image/png;base64," + base64.StdEncoding.EncodeToString(b)
	return
}

// createRemoteWinAccount 创建windows账户并将账户添加到 Remote Desktop Users
func (s HostPlugin) createRemoteWinAccount(account, pws string) (err error) {
	err = createWinAccount(account, pws)
	if err != nil {
		return
	}
	return accountAddGroup(account)
}

// createWinAccount 创建windows账户
func createWinAccount(account, pws string) (err error) {
	// 构建创建用户的命令
	cmd := exec.Command("net", "user", account, pws, "/add")
	// 执行命令并获取输出
	output, err := cmd.CombinedOutput()
	if err != nil {
		err = errors.New(fmt.Sprintf("创建用户时出错: %s，输出: %s", err, string(output)))
		return err
	}
	return nil
}

// accountAddGroup 将账户添加到 远程桌面用户组
func accountAddGroup(account string) (err error) {
	// 构建将用户添加到远程桌面用户组的命令
	cmd := exec.Command("net", "localgroup", "Remote Desktop Users", account, "/add")
	// 执行命令并获取输出
	output, err := cmd.CombinedOutput()
	if err != nil {
		err = errors.New(fmt.Sprintf("创建用户时出错: %s，输出: %s", err, string(output)))
		return err
	}
	return nil
}

// logoffSession 注销会话
func (s HostPlugin) logoffSession(id string) (err error) {
	cmd := exec.Command("logoff", id)
	// 执行命令并获取输出
	output, err := cmd.CombinedOutput()
	if err != nil {
		err = errors.New(fmt.Sprintf("注销会话时出错: %s，输出: %s", err, string(output)))
		return err
	}
	return nil
}

// mappingDir 将目录映射为盘符
func (s HostPlugin) mappingDir(dir string, driveCode string) (err error) {
	// 构建 subst 命令
	cmd := exec.Command("subst", driveCode+":", dir)
	// 执行命令并获取输出
	output, err := cmd.CombinedOutput()
	if err != nil {
		err = errors.New(fmt.Sprintf("映射目录到盘符时出错: %s，输出: %s", err, string(output)))
		return err
	}
	return nil
}

// unmapDrive 删除盘符映射
func (s HostPlugin) unmapDrive(driveCode string) (err error) {
	// 构建 subst 命令
	cmd := exec.Command("subst", driveCode+":", "/D")
	// 执行命令并获取输出
	output, err := cmd.CombinedOutput()
	if err != nil {
		err = errors.New(fmt.Sprintf("删除盘符映射时出错: %s，输出: %s", err, string(output)))
		return err
	}
	return nil
}

// createMonopolizeDir 创建独占目录
func (s HostPlugin) createMonopolizeDir(dir string, account string) (err error) {
	if !utils.IsExistFolder(dir) {
		err = os.MkdirAll(dir, os.ModePerm)
		if err != nil {
			return
		}
	}
	// 构建 icacls 命令.
	cmd := exec.Command("icacls", dir, "/grant", fmt.Sprintf("%s:(OI)(CI)F", account))
	// 执行命令并获取输出
	output, err := cmd.CombinedOutput()
	if err != nil {
		err = errors.New(fmt.Sprintf("账户: %s授权目录: %s 时出错: %s，输出: %s", account, dir, err, string(output)))
		return err
	}
	return nil
}
